#pragma once

#include <math.h>
#include <sstream>

class Vector3df
{
public:

	// 3d vector coordinates
	float m_coords[3];

	//
	// 3d vector constructors
	//
	Vector3df()
	{
		m_coords[0] = 0.0;
		m_coords[1] = 0.0;
		m_coords[2] = 0.0;
	}
	
	Vector3df(const float& x, const float& y, const float& z)
	{
		m_coords[0] = x;
		m_coords[1] = y;
		m_coords[2] = z;
	}
	
	Vector3df(const int& x, const int& y, const int& z)
	{
		m_coords[0] = (float)x;
		m_coords[1] = (float)y;
		m_coords[2] = (float)z;
	}

	//
	// 3d vector operators
	//
	Vector3df& operator+=(const Vector3df& v)
	{
		m_coords[0] += v.m_coords[0];
		m_coords[1] += v.m_coords[1];
		m_coords[2] += v.m_coords[2];
		return *this;
	}

	Vector3df& operator-=(const Vector3df& v)
	{
		m_coords[0] -= v.m_coords[0];
		m_coords[1] -= v.m_coords[1];
		m_coords[2] -= v.m_coords[2];
		return *this;
	}

	Vector3df& operator*=(const float& s)
	{
		m_coords[0] *= s;
		m_coords[1] *= s;
		m_coords[2] *= s;
		return *this;
	}

	Vector3df& operator/=(const float& s)
	{
		if(s == 0) return *this *= s;
		return *this *= (1.0f / s);
	}

	Vector3df& operator*=(const Vector3df v)
	{
		m_coords[0] *= v.m_coords[0];
		m_coords[1] *= v.m_coords[1];
		m_coords[2] *= v.m_coords[2];
		return *this;
	}

	float dot(const Vector3df& v) const
	{
		return (
			m_coords[0] * v.m_coords[0] + 
			m_coords[1] * v.m_coords[1] + 
			m_coords[2] * v.m_coords[2]);
	}
	
	float length2() const
	{
		return dot(*this);
	}

	float length() const
	{
		return sqrt(length2());
	}

	float distance(const Vector3df& v) const;

	Vector3df& normalize()
	{
		return *this /= length();
	}

	Vector3df cross(const Vector3df& v) const
	{
		return Vector3df(
			m_coords[1] * v.m_coords[2] - m_coords[2] * v.m_coords[1],
			m_coords[2] * v.m_coords[0] - m_coords[0] * v.m_coords[2],
			m_coords[0] * v.m_coords[1] - m_coords[1] * v.m_coords[0]);
	}

	bool operator==(const Vector3df& v) const
	{
		return (
			(m_coords[0] == v.m_coords[0]) &&
			(m_coords[1] == v.m_coords[1]) &&
			(m_coords[2] == v.m_coords[2]));
	}

	bool operator!=(const Vector3df& v) const
	{
		return !(*this == v);
	}

	void set(const float& x, const float& y, const float& z)
	{
		m_coords[0] = x;
		m_coords[1] = y;
		m_coords[2] = z;
	}

	// accessors
	const float& x() const { return m_coords[0]; }
	const float& y() const { return m_coords[1]; }
	const float& z() const { return m_coords[2]; }
	void setX(float x) { m_coords[0] = x; }
	void setY(float y) { m_coords[1] = y; }
	void setZ(float z) { m_coords[2] = z; }

	// for debugging
	const std::string toStr()
	{
		std::stringstream ss (std::stringstream::in | std::stringstream::out);
		ss << "(";
		ss << m_coords[0] << ", ";
		ss << m_coords[1] << ", ";
		ss << m_coords[2];
		ss << ")";
		return ss.str();
	}
};


Vector3df operator+(const Vector3df& v1, const Vector3df& v2);


Vector3df operator-(const Vector3df& v1, const Vector3df& v2);

Vector3df operator-(const Vector3df& v);

Vector3df operator*(const Vector3df& v1, const Vector3df& v2);

Vector3df operator*(const Vector3df& v, const float& s);

Vector3df operator*(const float& s, const Vector3df& v);

Vector3df operator/(const Vector3df& v, const float& s);